package com.hulk.boot;

import com.hulk.rpc.annotation.HulkFailover;
import com.hulk.rpc.annotation.HulkService;
import com.hulk.rpc.client.HulkClient;
import com.hulk.rpc.utils.ReflectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;

import javax.annotation.PreDestroy;
import java.lang.reflect.Field;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;

public class HulkClientStarter implements BeanFactoryPostProcessor, BeanPostProcessor, Ordered {
  private static final Log logger = LogFactory.getLog(HulkClientStarter.class);

  private ConfigurableListableBeanFactory beanFactory;
  private HulkClient hulkClient;

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;

    try {
      hulkClient = new HulkClient("hulk-client.conf");
    } catch (com.typesafe.config.ConfigException configException) {
      if (logger.isErrorEnabled()) {
        logger.error("hulk-client.conf 格式错误，无法开启HulkClient!", configException);
      }

      throw configException;
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error("类路径中找不到 hulk-client.conf，无法开启HulkClient!", e);
      }

      throw e;
    }

    Collection<Class<?>> hulkClassList = extractHulkServiceClassList(beanFactory);

    for (Class<?> hulkClass : hulkClassList) {
      registerHulkService(hulkClass);
    }
  }

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    tryInjectHulkServiceField(bean);
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof HulkClientAware) {
      ((HulkClientAware) bean).setHulkClient(hulkClient);
    }

    Class<?> clazz = bean.getClass();
    HulkFailover hulkFailover = clazz.getAnnotation(HulkFailover.class);

    if (hulkFailover == null) {
      return bean;
    }

    if (logger.isInfoEnabled()) {
      logger.info("扫描到Failover实例，重置HulkFailover: " + clazz.getName() + hulkFailover);
    }

    hulkClient.setFailover(hulkFailover.service(), bean);

    return bean;
  }

  @PreDestroy
  public void close() {
    if (hulkClient == null) {
      return;
    }

    try {
      hulkClient.close();
    } catch (Throwable e) {
      if (logger.isErrorEnabled()) {
        logger.error("HulkClient关闭失败!", e);
      }
    }
  }

  // 具体实现，下面的不用关注

  private void registerHulkService(Class<?> hulkClass) {
    if (hulkClient.getService(hulkClass) != null) {
      return;
    }

    String[] exists = beanFactory.getBeanNamesForType(hulkClass);
    // 如果已经注册则忽略，防止autowired byType冲突
    if (exists.length > 0) {
      if (logger.isInfoEnabled()) {
        logger.info("spring中已存在: " + hulkClass.getName() + ", 将不注册相应的远程服务对象, 通过反射对需要的字段直接赋值");
      }

      return;
    }

    String beanName = UPPER_CAMEL.to(LOWER_CAMEL, hulkClass.getSimpleName());

    if (logger.isWarnEnabled() && beanFactory.containsBean(beanName)) {
      String existClass = null;

      BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
      existClass = beanDefinition.getBeanClassName();

      if (existClass == null) {
        try {
          existClass = beanFactory.getBean(beanName).getClass().getName();
        } catch (Throwable t) {
          // ignore
        }
      }

      if (existClass != null) {
        String oldBeanName = beanName;
        beanName = hulkClass.getName();

        logger.warn("spring中存在冲突的类名: [" + oldBeanName + "] " //
                + existClass + "(existed), " + hulkClass.getName() + "(current)" //
                + ", 使用类全名称注册: " + beanName//
                + ", 将只能通过 byType 或者 @Qualifier(\"" + beanName + "\") 使用");
      }
    }

    hulkClient.registerService(hulkClass);
    hulkClient.setFailover(hulkClass, null);
    Object serviceBean = hulkClient.getService(hulkClass);

    // 无法设置为primary，要防止autowired byType冲突
    beanFactory.registerSingleton(beanName, serviceBean);

    if (logger.isInfoEnabled()) {
      logger.info("spring中注册远程服务: " + beanName + "@" + hulkClass.getName());
    }
  }

  private Collection<Class<?>> extractHulkServiceClassList(ConfigurableListableBeanFactory beanFactory) {
    LocalDateTime startTime = LocalDateTime.now();
    Set<Class<?>> hulkServiceSet = new HashSet<>();
    String[] beanNames = beanFactory.getBeanDefinitionNames();

    for (String beanName : beanNames) {
      BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
      String beanClassName = beanDefinition.getBeanClassName();

      extractHulkServiceClass(hulkServiceSet, beanClassName);
    }

    if (logger.isInfoEnabled()) {
      LocalDateTime finishTime = LocalDateTime.now();
      Duration duration = Duration.between(startTime, finishTime);

      String hulkServiceString = hulkServiceSet//
              .stream()//
              .map(Class::getName)//
              .collect(Collectors.joining(",", "[", "]"));

      logger.info("扫描到HulkService: " + hulkServiceString);
      logger.info("扫描HulkService耗时: " + duration);
    }

    return hulkServiceSet;
  }

  private void extractHulkServiceClass(Set<Class<?>> hulkServiceSet, String beanClassName) {
    if (beanClassName == null || beanClassName.startsWith("org.springframework.")) {
      return;
    }

    Class<?> beanClass;
    try {
      beanClass = Class.forName(beanClassName, false, beanFactory.getBeanClassLoader());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }

    Collection<Class<?>> allDependClass = ReflectUtils.getAllDependClass(beanClass, clazz -> {
      if (!clazz.isInterface()) {// 只支持接口
        return false;
      }

      HulkService hulkService = clazz.getAnnotation(HulkService.class);

      return hulkService != null;
    });

    hulkServiceSet.addAll(allDependClass);
  }

  private void tryInjectHulkServiceField(Object bean) {
    if (bean == null) {
      return;
    }

    Class<?> beanClass = bean.getClass();

    while (beanClass != Object.class) {
      tryInjectHulkServiceField(beanClass, bean);
      beanClass = beanClass.getSuperclass();
    }
  }

  private void tryInjectHulkServiceField(Class<?> beanClass, Object bean) {
    Field[] fields = beanClass.getDeclaredFields();
    for (Field field : fields) {
      Class<?> fieldClass = field.getType();

      if (!fieldClass.isInterface()) {// 只支持接口
        continue;
      }

      HulkService hulkService = fieldClass.getAnnotation(HulkService.class);

      if (hulkService == null) {
        continue;
      }

      Object serviceBean = hulkClient.getService(fieldClass);
      if (serviceBean == null) {
        hulkClient.registerService(fieldClass);
        hulkClient.setFailover(fieldClass, null);

        serviceBean = hulkClient.getService(fieldClass);
      }

      try {
        field.setAccessible(true);
        field.set(bean, serviceBean);
      } catch (Throwable t) {
        if (logger.isErrorEnabled()) {
          logger.error("手动注入HulkService失败，" + field, t);
        }
      }
    }
  }
}
